#  Copyright (C) 2010 Luca Piccinelli
# 
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''
Created on May 7, 2011

@author: picci
'''

from PyQt4 import QtGui, QtCore
from computation_widget import Ui_computation_widget
from pipeit import GeneratorFactoriesPool_get_instance, bad_class
from pypelab.ui.visitors.pdlvisitors import QtGuiPDLVisitor                   
from pypelab import globals
from pypelab.ui.rendering import RenderProperties
import pypelab.ui.pipeline
import logging

class IOKeyLabel(QtGui.QLabel):
    #****** Computation Widget ******************************************
    """ A QLabel extension that references an IOKey """                
    #********************************************************************
    def __init__(self, k, pipestepDescriptor, mainContainer, parent = None, text = None):
        if not text: text = k.get_key()
        super().__init__(text, parent)
        self.mainContainer = mainContainer
        self.pipestepDescriptor = pipestepDescriptor
        self.key = k
        
        self.group = None
                
        # list of all incoming or outgoing arcs of this label 
        self.bridge_arc_list= []
        
        self.draggables = ['generators']
    
    def addArc(self, arc):    
        self.bridge_arc_list.append(arc)
        
    def removeArc(self, arc):    
        self.bridge_arc_list.remove(arc)
        
    def dragEnterEvent(self, evt):
        if evt.source().accessibleName() not in self.draggables and type(evt.source()) != IOKeyGroup:
            evt.ignore()
            return 
        
        model = QtGui.QStandardItemModel()
        model.dropMimeData(evt.mimeData(), QtCore.Qt.CopyAction, 0, 0, QtCore.QModelIndex())
        
        # get generator data from the drag obj
        generator_data = model.item(0).text().split(',')
        
        if len(generator_data) > 1: # if len > 1, i'm dragging a generator, to create a BridgeGenerar
            self.from_generator_id, self.generator_id, self.iokey_str = generator_data
        else:                       # otherwise i'm dragging a generator to simply create it
            self.from_generator_id = self.generator_id = generator_data[0]
            
        try:
            from_generator = GeneratorFactoriesPool_get_instance().build(self.from_generator_id)
        except bad_class as ex:
            logging.warn(ex.what())
            evt.ignore()
            return
        
        if self.key.getType() == from_generator.get_key().getType():
            evt.acceptProposedAction()
        else:
            evt.ignore()
            
    def dropEvent(self, evt):
        self.group = IOKeyGroup(self, self.generator_id, self.parent())
        if type(evt.source()) is IOKeyGroup:
            for k, l in self.group.confItems():
                if k.get_key() ==  globals.GENERATOR_ID_IOKEY:
                    l.buddy().setValue(self.iokey_str)
                    l.buddy().setEnabled(False)
                    
            source_label = evt.source().label
            widgetBuilder = pypelab.ui.pipeline.ComputationWidgetBuilder()
            widgetBuilder.connectGroups(source_label, self)
            
        evt.acceptProposedAction()
        
       
class IOKeyGroup(QtGui.QGroupBox):
    #****** Computation Widget ******************************************
    """ A QGroupBox extension that references an IOKey """                
    #********************************************************************
    def __init__(self, label = None, generator_id = None, parent = None):
        if not label or not generator_id: return
        
        text = '{0}: {1}'.format(label.key.get_key(), generator_id)
        super().__init__(text, parent)
        
        self.generator_id = generator_id
        self.labels = {}
        
        label.hide()
        
        # close observers
        self.close_observers = []
        
        # positioning this widget inside the parent layout
        self.setAlignment(QtCore.Qt.AlignLeft)
        layout = label.parent().layout()
        label_idx = layout.indexOf(label)
        self.label_position = layout.getItemPosition(label_idx) 
        self.label = label
        layout.addWidget(self, self.label_position[0], self.label_position[1])
        
        # layout of elements of this widget
        self.layout = QtGui.QGridLayout()
        self.layout.setAlignment(QtCore.Qt.AlignLeft)
        self.setLayout(self.layout)
        
        # widgets
        self.close_pb = QtGui.QPushButton(QtGui.QIcon.fromTheme(("edit-delete")), "", self)
        self.close_pb.setMaximumWidth(25)
        self.close_pb.setFlat(True)
        
        # add widgets to layout
        pdl_visitor = QtGuiPDLVisitor()
        conf_template = GeneratorFactoriesPool_get_instance().get_configuration_template(label.generator_id)
        for i in range(conf_template.parameters_num()):
            key = conf_template.param_at(i)
            if key.getType() != globals.PARAM_IOKEY_TYPE:
                param_label = IOKeyLabel(key, 
                                         label.pipestepDescriptor, 
                                         label.mainContainer, 
                                         label.mainContainer.ui.criteria_area, 
                                         key.get_key())
                wid = PDLWidget(self)
                param_label.setBuddy(wid)
                self.labels[key] = param_label
                pypelab.ui.pipeline.build_pdl_widget(wid, pdl_visitor, key)
                
                self.layout.addWidget(param_label, i, 1)
                self.layout.addWidget(wid, i, 2)
                    
                    
        self.layout.addWidget(self.close_pb)
        
        # connect events
        QtCore.QObject.connect(self.close_pb, QtCore.SIGNAL("clicked()"), self.closeGroup)
        
    def confItems(self):
        return self.labels.items()

    def confValues(self):
        return self.labels.values()
        
    def mousePressEvent(self, evt):
        if evt.button() == QtCore.Qt.LeftButton:
            self.dragStartPosition = evt.pos()
            
    def mouseMoveEvent(self, evt):
        if not evt.buttons() & QtCore.Qt.LeftButton:
            return
        if (evt.pos() - self.dragStartPosition).manhattanLength() < QtGui.QApplication.startDragDistance():
            return

        # compose drag information
        drag  = QtGui.QDrag(self)
        model = QtGui.QStandardItemModel()
        
        # compose the ID of the start generator 
        gen_id = QtGui.QStandardItem('{0},{1},{2}'.format(self.generator_id, 
                                                          globals.BRIDGE_GENERATOR_ID, 
                                                         '{0}{1}{2}'.format(self.label.pipestepDescriptor.get_id(),
                                                                            globals.PIPESTEP_ID_SEP, 
                                                                            self.label.key.get_key()) ))
        model.appendRow(gen_id)
        
        #set mime data 
        drag.setMimeData(model.mimeData( [model.index(0, 0)] ))
        action = drag.exec(QtCore.Qt.CopyAction)
            
    def closeNotified(self):
        self.closeGroup()        
        
    def closeGroup(self):
        # notify all arc observers to delete the arc
        for arc in self.label.bridge_arc_list: arc.delete()
        # notify the close to all close observers
        for obs in self.close_observers: obs.closeNotified()
        
        self.close()
        self.label.group = None
        self.label.show()
        
        globals.pipeWorkArea.viewport().update()
        
    def addCloseObserver(self, observer):
        self.close_observers.append(observer)
            

class PDLWidget(QtGui.QWidget):
    #****** ComputationRangeWidget ***************************************
    """ A Slider with its own label """                
    #********************************************************************
    def __init__(self, parent = None):
        super().__init__(parent)
        self.setupUi()
        
    def setupUi(self):
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        self.verticalLayout = QtGui.QVBoxLayout(self)
        
    def connect(self):
        if self.verticalLayout.count() == 0: return
        
        self.input_widget = self.verticalLayout.itemAt(0).widget()
        
        if type(self.input_widget) == QtGui.QSlider:
            self.scale = self.input_widget.property('scale')
            self.verticalLayout.removeWidget(self.input_widget)
            
            self.value_label = QtGui.QLabel(str(self.input_widget.value()), self)
            self.value_label.setBuddy(self.input_widget)
            
            self.verticalLayout.addWidget(self.value_label)
            self.verticalLayout.addWidget(self.input_widget)
            
            QtCore.QObject.connect(self.input_widget, QtCore.SIGNAL("valueChanged(int)"), self.setSliderText)
            
    def setSliderText(self):
        if self.value_label: self.value_label.setText(str(self.input_widget.value() * self.scale))
        
    def value(self):
        if type(self.input_widget) == QtGui.QSlider:
            return self.input_widget.value() * self.scale
        elif type(self.input_widget) == QtGui.QComboBox:
            return self.input_widget.currentIndex()
        elif type(self.input_widget) == QtGui.QLineEdit:
            return self.input_widget.text()
    
    def setValue(self, value):
        if type(self.input_widget) == QtGui.QSlider:
            self.input_widget.setValue(float(value) * (1.0 / self.scale))
        elif type(self.input_widget) == QtGui.QComboBox:
            self.input_widget.setCurrentIndex(int(value))
        elif type(self.input_widget) == QtGui.QLineEdit:
            self.input_widget.setText(value)
        

class PipeWorkAreaMdiSubWindow(QtGui.QMdiSubWindow):
    #****** Computation Widget ******************************************
    """ This is a QMdiWorkArea that redefines moveEvent """                
    #********************************************************************
    def __init__(self, parent = None):
        super().__init__(parent)
        
        # objects that are notified when the widget moves
        self.move_observers = [] 
    
    def addMoveObserver(self, observer):    
        self.move_observers.append(observer)
        
    def moveEvent(self, evt):
        for obs in self.move_observers:
            obs.moveNotified(evt.pos() - evt.oldPos())
            
        globals.pipeWorkArea.viewport().update()

class ComputationWidget(QtGui.QWidget):
    #****** Computation Widget ******************************************
    """ This is the widget that interfaces to the underlying computation 
        parameters """                
    #********************************************************************
    def __init__(self, parent = None):
        super().__init__(parent)
        self.ui = Ui_computation_widget()
        self.ui.setupUi(self)
        
        self.pipeStepDescriptor = None
        #accepted types: it depends from the computation set
        self.in_types = []  # input types
        self.out_types = [] # output types
        self.types = {self.ui.step_interface.tabText(0):self.in_types,
                      self.ui.step_interface.tabText(1):self.out_types, 
                      self.ui.step_interface.tabText(2):None}
        
        #labels
        self.in_labels = []       # labels of input tab
        self.out_labels = []      # labels of output tab
        self.criteria_labels = [] # labels of criteria tab
        self.labels = {self.ui.step_interface.tabText(0):self.in_labels,
                       self.ui.step_interface.tabText(1):self.out_labels,
                       self.ui.step_interface.tabText(2):self.criteria_labels}
        
        self.draggables = ['generators']
        
        # list of all incoming or outgoing arcs of this computation widget
        self.bridge_arc_list = []
        
        # tmp variables
        self.tmp_obj = None
        
        # close observers
        self.close_observers = []
        
    def id(self):
        if not self.pipeStepDescriptor: return None
        return self.pipeStepDescriptor.get_id()
    
    def keys(self):
        keys = {label.key : label for label in self.in_labels}
        keys.update({label.key : label for label in self.out_labels})
        keys.update({label.key : label for label in self.criteria_labels})
        return keys  
        
    def addArc(self, arc):    
        self.bridge_arc_list.append(arc) 
        
    def removeArc(self, arc):    
        self.bridge_arc_list.remove(arc)       
        
    def dragEnterEvent(self, evt):
        if evt.source().accessibleName() in self.draggables:
            generator_id = evt.source().selectedIndexes()[0].data()
            try:
                self.tmp_obj = GeneratorFactoriesPool_get_instance().build(generator_id)
            except bad_class as ex:
                logging.warn(ex.what())
                evt.ignore()
                return
            
            # select tab 
            tab_text = self.ui.step_interface.tabText(self.ui.step_interface.currentIndex())
            types  = self.types[tab_text]            
            if not types:
                evt.ignore()
                if self.tmp_obj: del self.tmp_obj
                return            
            labels = self.labels[tab_text]  
                
            # color labels
            if self.tmp_obj.get_key().getType() in types:
                evt.acceptProposedAction()
                self.setFocus(QtCore.Qt.MouseFocusReason)
                for label in labels:
                    key = label.key
                    if key.getType() == self.tmp_obj.get_key().getType():
                        palette = label.palette() 
                        palette.setColor(label.foregroundRole(), RenderProperties().enabled_color);
                        label.setPalette(palette)
            else:
                evt.ignore()
                if self.tmp_obj: del self.tmp_obj
        else:
            evt.ignore()
            
    def dragLeaveEvent(self, evt):
        self.resetColors()
        
    def dropEvent(self, evt):
        self.resetColors()    
        
    def resetColors(self):
        labels = self.labels[self.ui.step_interface.tabText(self.ui.step_interface.currentIndex())]   
        for label in labels:
            palette = label.palette()
            if palette.color(label.foregroundRole()) != QtCore.Qt.black:
                palette.setColor(label.foregroundRole(), QtCore.Qt.black);
                label.setPalette(palette)            
                
    def closeEvent(self, evt):
        # notify the arcs to delete themselves
        while len(self.bridge_arc_list) > 0: self.bridge_arc_list[0].delete()
        # notify the close to all close observers
        for obs in self.close_observers: obs.closeNotified() 
        
        globals.pipeWorkArea.viewport().update()
        super().closeEvent(evt)
        
    def addCloseObserver(self, observer):
        self.close_observers.append(observer)     